﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Specialized;
using System.Collections;
using UMC.Data;
using UMC.Net;

namespace UMC.Web
{


    /// <summary>
    /// 请求的单据
    /// </summary>
    public class WebClient
    {

        public static void Register(Type type)
        {
            WebRuntime.Register(type);
        }

        public static void Register(Func<WebActivity> t)
        {

            WebRuntime.Register(t);
        }
        public static void Register(Func<WebFlow> t)
        {

            WebRuntime.Register(t);
        }
        public static void Register(Func<IWebFactory> t)
        {
            WebRuntime.Register(t);
        }
        class CommandKey
        {
            public string cmd
            {
                get;
                set;
            }
            public string model
            {
                get;
                set;
            }
            public string value
            {
                get;
                set;
            }

        }

        internal const int OuterDataEvent = 131072;

        /// <summary>
        /// 扩展
        /// </summary>
        internal static readonly WebEvent Prompt = (WebEvent)2048;

        public bool? IsVerify
        {
            get;
            private set;
        }
        internal WebRuntime _runtime;
        public void Clear(WebEvent Event)
        {
            if ((this.ClientEvent & Event) == Event)
            {
                this.ClientEvent = this.ClientEvent ^ Event;
            }
        }
        internal String Server
        {
            get
            {
                return this._context.Server;
            }
        }
        public System.Collections.Hashtable OuterHeaders => _OuterHeaders;
        System.Collections.Hashtable _OuterHeaders = new Hashtable();

        public WebEvent ClientEvent
        {
            get;
            private set;
        }
        public int RedirectTimes { get; protected set; }


        public Uri Uri
        {
            get
            {

                return this._context.Url;
            }
        }
        public string UserHostAddress
        {
            get
            {
                return this._context.UserHostAddress;
            }
        }
        // public ulong? RequestId
        // {
        //     get;
        //     protected set;
        // }
        public Uri UrlReferrer
        {
            get
            {
                return this._context.UrlReferrer;
            }
        }

        public bool IsApp { get; set; }
        public UMC.Security.AccessToken Token
        {
            get
            {
                return _context.Token;
            }
        }
        public bool IsCashier { get; set; }
        // String _UserAgent;
        public StringValue UserAgent
        {
            get
            {
                return this._context.Headers.Get("User-Agent"u8);
            }
        }

        static bool CheckApp(StringValue UserAgent)
        {
            if (UserAgent.IsEmpty == false)
            {
                return UserAgent.Contains("UMC Client"u8);
            }
            return false;
        }
        // internal string _jsonp;
        Net.NetContext _context;
        public WebClient(Net.NetContext context)
        {
            this._context = context;
            if (Utility.TryParse(context.Headers.Get("umc-redirect-times"u8).Span, 10, out var _v))
            {
                this.RedirectTimes = _v;
            }
            else
            {
                this.RedirectTimes = 0;
            }

            this.IsCashier = context.Token.IsInRole(UMC.Security.AccessToken.UserRole);
            this.IsApp = CheckApp(context.UserAgent);

        }

        //internal int XHRTime = 0;

        Queue<CommandKey> _cmds;// = new Queue<CommandKey>();
        Hashtable _Finish;//= new Hashtable();


        void QueueTo()
        {
            var cmd = _cmds.Dequeue(); //_cmds.Peek();
            if ((Convert.ToInt32(this.ClientEvent) & OuterDataEvent) == OuterDataEvent)
            {
                var data = this.OuterHeaders["Data"];
                _Finish[$"{cmd.model}.{cmd.cmd}.{cmd.value}"] = data;
                if (data is System.Uri)
                {
                    _Finish[$"{cmd.model}.{cmd.cmd}.{cmd.value}"] = (data as System.Uri).AbsoluteUri;
                }
                else
                {
                    var sb = new StringBuilder();
                    var writer = new System.IO.StringWriter(sb);
                    UMC.Data.JSON.Serialize(data, writer);
                    writer.Flush();

                    _Finish[$"{cmd.model}.{cmd.cmd}.{cmd.value}"] = UMC.Data.JSON.Expression(sb.ToString());
                }
            }
            else
            {

                var sb = new StringBuilder();
                var writer = new System.IO.StringWriter(sb);

                writer.Write('{');
                writer.Write("\"ClientEvent\":{0}", Convert.ToInt32(this.ClientEvent));
                if (this.OuterHeaders != null && this.OuterHeaders.Count > 0)
                {
                    writer.Write(",\"Headers\":");
                    UMC.Data.JSON.Serialize(this.OuterHeaders, writer);
                }
                if (RedirectTimes > 0 && _Redirect != null)
                {
                    writer.Write(",\"Redirect\":");
                    UMC.Data.JSON.Serialize(this._Redirect, writer);
                }
                writer.Write('}');

                writer.Flush();

                _Finish[$"{cmd.model}.{cmd.cmd}.{cmd.value}"] = UMC.Data.JSON.Expression(sb.ToString());

            }
            if (_cmds.Count == 0)
            {
                _context.ContentType = new StringValue("text/javascript; charset=utf-8".UTF8()); ;
                var writer = _context.Output;
                UMC.Data.JSON.Serialize(_Finish, writer);
                writer.Flush();
            }
            else
            {
                Command(_cmds.Peek());
            }
        }
        void Command(CommandKey c)
        {
            this.ClientEvent = WebEvent.None;
            _Output = QueueTo;
            if (Verify(c.model, c.cmd))
            {
                if (String.IsNullOrEmpty(c.value))
                {
                    this.Redirect(c.model, c.cmd, String.Empty);
                }
                else if (c.value.IndexOf("=") > -1)
                {
                    var QueryString = System.Web.HttpUtility.ParseQueryString(c.value) ?? new NameValueCollection();
                    this.Redirect(c.model, c.cmd, QueryString);
                }
                else
                {
                    this.Redirect(c.model, c.cmd, c.value);
                }
            }
            else
            {
                QueueTo();
            }

        }
        public bool IsContent
        {
            get; internal set;
        }
        public void Command(string json)
        {
            _Finish = new Hashtable();
            var cmds = UMC.Data.JSON.Deserializes<WebMeta>(json);

            _cmds = new Queue<CommandKey>();

            foreach (var c3 in cmds)
            {
                _cmds.Enqueue(new CommandKey { cmd = c3["cmd"], model = c3["model"], value = c3["value"] });
            }
            ;
            if (cmds.Length > 0)
            {
                Command(_cmds.Peek());
            }
            else
            {
                this._WriteTo();
            }
        }
        public void Command(string model, string cmd, string value)
        {

            _Output = this._WriteTo;
            if (Verify(model, cmd))
            {
                Redirect(model, cmd, value);
            }
            else
            {
                _Output();
            }
        }
        void Redirect(string model, string cmd, string value)
        {
            var hash = new Hashtable();
            if (!String.IsNullOrEmpty(value))
            {
                hash[cmd] = value;
            }
            this.Send(model, cmd, hash);

        }
        public bool Verify(UMC.Security.Identity user, int site, string model, string cmd, Dictionary<String, Tuple<WebAuthType, bool>> auths, out bool isBiometric)
        {
            if (UMC.Data.Reflection.Instance().IsAuthorization(user, site, new StringValue($"UMC/{model}/{cmd}"), out isBiometric) == false)
            {
                return false;
            }
            String key = $"{model}.{cmd}";

            Tuple<WebAuthType, bool> authType;
            if (auths.TryGetValue(key, out authType) == false)
            {
                if (auths.TryGetValue(model, out authType) == false)
                {
                    return true;
                }
            }
            if (isBiometric == false)
            {
                isBiometric = authType.Item2;
            }

            switch (authType.Item1)
            {
                case WebAuthType.All:
                    return true;
                case WebAuthType.User:
                    if (user.IsInRole(Security.AccessToken.UserRole))
                    {
                        return true;
                    }
                    break;
                case WebAuthType.UserCheck:
                    if (user.IsInRole(Security.AccessToken.UserRole))
                    {
                        return true;
                    }
                    break;
                case WebAuthType.Check:
                    return true;
                case WebAuthType.Admin:
                    if (user.IsInRole(Security.AccessToken.AdminRole))
                    {
                        return true;
                    }
                    break;
                case WebAuthType.Guest:
                    if (user.IsAuthenticated)
                    {
                        return true;
                    }
                    break;
                    // case WebAuthType.UIEvent:
                    //     return AuthUIEvent(model, cmd);//.// RedirectTimes > 0;
            }
            return false;
        }
        // protected virtual bool AuthUIEvent(string model, string cmd)
        // {
        //     return true;
        // }
        bool IsBiometric = false;
        bool Verify(string model, string cmd)
        {
            if (this.IsVerify.HasValue == false)
            {
                var user = this.Token.Identity();

                if (Verify(user, 0, model, cmd, WebRuntime.authKeys, out var isBiometric))
                {
                    if (isBiometric && this.Context.Token.BiometricTime == 0)
                    {
                        IsBiometric = true;
                    }
                    this.IsVerify = true;
                    return true;
                }
                // this.OuterHeaders = new Hashtable();
                if (user.IsAuthenticated)
                {
                    this.ClientEvent = WebEvent.Prompt;
                    this.OuterHeaders["Prompt"] = new WebMeta().Put("Title", "提示", "Text", "权限受限");
                }
                else
                {
                    this.ClientEvent = WebEvent.Prompt | WebEvent.DataEvent;
                    this.OuterHeaders["Prompt"] = new WebMeta().Put("Title", "提示", "Text", "您没有登录,请登录");
                    this.OuterHeaders["DataEvent"] = new WebMeta().Put("type", "Login");
                }
                return false;

            }
            return true;
        }


        /// <summary>
        /// 当前处理共享健值
        /// </summary>
        internal System.Collections.Hashtable Items = new System.Collections.Hashtable();

        public void Command(string model, string cmd, NameValueCollection queryString)
        {

            _Output = this._WriteTo;
            if (Verify(model, cmd))
            {
                Redirect(model, cmd, queryString);
            }
            else
            {
                _Output();
            }

        }
        void Redirect(string model, string cmd, NameValueCollection QueryString)
        {

            switch (QueryString.Count)
            {
                case 0:
                    this.Redirect(model, cmd, String.Empty);
                    break;
                case 1:
                    var skey = QueryString.GetKey(0);
                    var svalue = QueryString.Get(0);

                    if (String.IsNullOrEmpty(svalue))
                    {
                        this.Redirect(model, cmd, skey);
                    }
                    else
                    {
                        goto default;
                    }
                    break;
                default:
                    var sendValue = new System.Collections.Hashtable();

                    var header = new System.Collections.Hashtable();
                    for (var i = 0; i < QueryString.Count; i++)
                    {
                        var key = QueryString.GetKey(i);
                        if (String.IsNullOrEmpty(key) || String.Equals(key, "_"))
                        {
                            var values = QueryString.GetValues(i);
                            if (values.Length > 1)
                            {
                                header[cmd] = values;
                            }
                            else
                            {

                                header[cmd] = values[0];
                            }
                        }
                        else
                        {
                            sendValue[key] = QueryString.Get(i);
                        }
                    }
                    header[model] = sendValue;
                    this.Send(model, cmd, header);
                    break;
            }

        }


        void OutputHeader(UMC.Web.WebMeta header)
        {
            var dic = header.GetDictionary();

            var em = dic.GetEnumerator();
            while (em.MoveNext())
            {
                var key = em.Key.ToString();
                switch (key)
                {
                    case EventType.UIEvent:
                    case EventType.DataEvent:
                    case EventType.AsyncDialog:
                        var value = em.Value;
                        if (this.OuterHeaders.ContainsKey(key))
                        {
                            var ats = new System.Collections.ArrayList();
                            var ts = this.OuterHeaders[key];

                            if (ts is Array)
                            {
                                ats.AddRange((Array)ts);
                            }
                            else
                            {

                                ats.Add(ts);

                            }
                            if (value is Array)
                            {

                                ats.AddRange((Array)value);
                            }
                            else
                            {
                                ats.Add(value);
                            }
                            this.OuterHeaders[key] = ats.ToArray();
                        }
                        else
                        {
                            this.OuterHeaders[em.Key] = em.Value;
                        }
                        break;
                    default:
                        this.OuterHeaders[em.Key] = em.Value;
                        break;
                }
            }

        }
        WebMeta _Redirect;
        internal void Atfer(WebContext context)
        {

            var request = context.Request;
            var response = context.Response;
            var webEvent = response.ClientEvent;
            if ((Convert.ToInt32(webEvent) & OuterDataEvent) == OuterDataEvent)
            {
                var data = response.Headers.GetDictionary()["Data"];
                if (data is WebActivity)
                {
                    response.ClientEvent = WebEvent.None;
                    response.Headers.Remove("Data");

                    _context.UseSynchronousIO();

                }
                else
                {
                    this.ClientEvent = webEvent;
                    this.OutputHeader(response.Headers);
                    this._Output();
                }
                return;
            }

            if (webEvent != WebEvent.None)
            {


                OutputCheck(context);


                this.ClientEvent |= response.ClientEvent;
                OutputHeader(response.Headers);
            }

            var redirect = response.ClientRedirect;
            if (redirect != null)
            {
                this.RedirectTimes++;
                if (this.RedirectTimes > 10)
                {

                    this.ClientEvent = WebEvent.Prompt;
                    this.OuterHeaders.Clear();
                    this.OuterHeaders["Prompt"] = new WebMeta().Put("Title", "提示", "Text", String.Format("{0}.{1},请求重定向超过最大次数", redirect.Model, redirect.Command));

                    _Output();

                }
                else
                {
                    if (String.IsNullOrEmpty(redirect.Value))
                    {
                        this.Redirect(redirect.Model, redirect.Command, String.Empty);
                    }
                    else
                    {
                        if (redirect.Value.StartsWith("{"))
                        {
                            var p = UMC.Data.JSON.Deserialize(redirect.Value) as Hashtable;
                            var queryString = new NameValueCollection();
                            var em = p.GetEnumerator();
                            while (em.MoveNext())
                            {
                                var key = em.Key as string;
                                switch (key)
                                {
                                    case "__":
                                        foreach (var pv in em.Value.ToString().Split('/', StringSplitOptions.RemoveEmptyEntries))
                                        {
                                            queryString.Add("_", pv);
                                        }
                                        break;
                                    default:
                                        queryString.Add(key, em.Value.ToString());
                                        break;
                                }
                            }
                            this.Redirect(redirect.Model, redirect.Command, queryString);
                        }
                        else
                        {
                            this.Redirect(redirect.Model, redirect.Command, redirect.Value);
                        }
                    }
                }
                return;
            }

            if (context.runtime.CurrentActivity == null)
            {
                _Redirect = new WebMeta().Put("model", context.Request.Model, "cmd", context.Request.Command);
                var sv = request.SendValues ?? new WebMeta();

                if (String.IsNullOrEmpty(request.SendValue) == false)
                {
                    if (sv.Count > 0)
                    {
                        sv.Put("_", request.SendValue);
                        _Redirect.Put("send", sv);
                    }
                    else
                    {
                        _Redirect.Put("send", request.SendValue);
                    }

                }
                else if (sv.Count > 0)
                {

                    _Redirect.Put("send", sv);
                }

            }
            else
            {
                this.Completed();
            }
            _Output();
        }
        protected virtual void Completed()
        {
            // this.Context
        }
        protected virtual void OutputCheck(WebContext context)
        {
        }
        void Send(String model, String cmd, System.Collections.IDictionary value)
        {
            if (this.IsBiometric)
            {
                this.IsBiometric = false;

                var send = value.Contains(model) ? new WebMeta(value[model] as Hashtable) : new WebMeta();
                var vs = value[cmd];
                if (vs is string)
                {
                    send.Put("_", vs);
                }
                else if (vs is string[])
                {
                    var stv = vs as string[];
                    if (stv.Length > 0)
                    {
                        send.Put("__", String.Join('/', stv));
                    }
                }
                var v = new Hashtable();
                if (send.Count > 0)
                {
                    v["oauth_callback"] = $"/UMC/{model}/{cmd}?{UMC.Data.JSON.Serialize(send)}"; ;
                }
                else
                {
                    v["oauth_callback"] = $"/UMC/{model}/{cmd}"; ;
                }
                value.Clear();
                value["Account"] = v;
                var context = WebRuntime.ProcessRequest("Account", "Biometric", value, this);
                Atfer(context);
            }
            else
            {
                var context = WebRuntime.ProcessRequest(model, cmd, value, this);

                Atfer(context);

            }

        }



        public Net.NetContext Context
        {
            get
            {
                return _context;
            }
        }
        Action _Output;
        void _WriteTo()
        {
            var writer = _context.Output;
            if ((Convert.ToInt32(this.ClientEvent) & OuterDataEvent) == OuterDataEvent)
            {
                var data = this.OuterHeaders["Data"];

                if (data is System.Uri)
                {
                    if (_context.Method== HttpMethod.GET)
                    {

                        _context.Redirect((data as System.Uri).AbsoluteUri);
                        _context.OutputFinish();
                        return;
                    }
                    else
                    {

                        if (_context.Path.Span.EndsWith("/"u8))
                        {
                            _context.Redirect((data as System.Uri).AbsoluteUri);
                            _context.OutputFinish();
                        }
                        else
                        {
                            this.ClientEvent = WebEvent.DataEvent;
                            this.OuterHeaders.Clear();
                            this.OuterHeaders["DataEvent"] = new WebMeta().Put("type", "Url", "value", (data as System.Uri).AbsoluteUri);
                        }
                    }

                }
                else
                {
                    _context.ContentType = StringValue.ContentTypeJS;// new StringValue("text/javascript;charset=utf-8".UTF8());
                    UMC.Data.JSON.Serialize(data, writer);
                    writer.Flush();
                    _context.OutputFinish();
                    return;
                }
            }
            _context.ContentType = StringValue.ContentTypeJS;//new StringValue("text/javascript;charset=utf-8".UTF8());
            if (this.IsContent)
            {
                UMC.Data.JSON.Serialize(this.OuterHeaders[EventType.AsyncDialog] ?? this.OuterHeaders, writer);
            }
            else
            {

                writer.Write('{');
                writer.Write("\"ClientEvent\":{0}", Convert.ToInt32(this.ClientEvent));
                if (this.OuterHeaders.Count > 0)
                {
                    writer.Write(",\"Headers\":");
                    UMC.Data.JSON.Serialize(this.OuterHeaders, writer);
                }
                if (RedirectTimes > 0 && _Redirect != null)
                {
                    writer.Write(",\"Redirect\":");
                    UMC.Data.JSON.Serialize(this._Redirect, writer);
                }
                writer.Write('}');

            }
            writer.Flush();
            _context.OutputFinish();
        }

    }

}
